#include "CylinderGeometry.h"
POCELAIN_NAMESPACE_BEGIN
CylinderGeometry::CylinderGeometry(
    Real radiusTop,
    Real radiusBottom,
    Real height,
    uint32_t radialSegments,
    uint32_t heightSegments,
    Real openEnded,
    Real thetaStart,
    Real thetaLength)
    : radiusTop(radiusTop)
    , radiusBottom(radiusBottom)
    , height(height)
    , radialSegments(radialSegments)
    , heightSegments(heightSegments)
    , openEnded(openEnded)
    , thetaStart(thetaStart)
    , thetaLength(thetaLength)
{
    radialSegments = std::floor( radialSegments );
    heightSegments = std::floor( heightSegments );

    // buffers
    std::vector<uint32_t> indices;
    std::vector<Real> vertices;
    std::vector<Real> normals;
    std::vector<Real> uvs;

    // helper variables

    uint32_t index = 0;
    std::vector<std::vector<uint32_t>> indexArray;
    Real halfHeight = height / 2;
    size_t groupStart = 0;

    // generate geometry
    auto  generateTorso = [&]()
    {

        Vector3 normal;
        Vector3 vertex;

        size_t groupCount = 0;

        // this will be used to calculate the normal
        Real slope = ( radiusBottom - radiusTop ) / height;

        // generate vertices, normals and uvs
        for ( uint32_t y = 0; y <= heightSegments; ++y )
        {
            std::vector<uint32_t> indexRow;
            Real v = Real(y) / heightSegments;

            // calculate the radius of the current row
            Real radius = v * ( radiusBottom - radiusTop ) + radiusTop;
            for ( uint32_t x = 0; x <= radialSegments; ++x )
            {
                Real u = Real(x) / radialSegments;
                Real theta = u * thetaLength + thetaStart;

                Real sinTheta = std::sin( theta );
                Real cosTheta = std::cos( theta );

                // vertex
                vertex.x = radius * sinTheta;
                vertex.y = - v * height + halfHeight;
                vertex.z = radius * cosTheta;

                vertices.push_back(vertex.x);
                vertices.push_back(vertex.y);
                vertices.push_back(vertex.z);

                // normal

                normal.set( sinTheta, slope, cosTheta ).normalize();
                normals.push_back(normal.x);
                normals.push_back(normal.y);
                normals.push_back(normal.z);

                // uv
                uvs.push_back(u);
                uvs.push_back(1 - v);

                // save index of vertex in respective row
                indexRow.push_back( index ++ );

            }

            // now save vertices of the row in our index array

            indexArray.push_back( indexRow );

        }

        // generate indices

        for (uint32_t x = 0; x < radialSegments; ++x)
        {
            for ( uint32_t y = 0; y < heightSegments; ++y)
            {
                // we use the index array to access the correct indices
                auto a = indexArray[ y ][ x ];
                auto b = indexArray[ y + 1 ][ x ];
                auto c = indexArray[ y + 1 ][ x + 1 ];
                auto d = indexArray[ y ][ x + 1 ];

                // faces
                indices.push_back(a);
                indices.push_back(b);
                indices.push_back(d);

                indices.push_back(b);
                indices.push_back(c);
                indices.push_back(d);

                // update group counter
                groupCount += 6;
            }
        }

        // add a group to the geometry. this will ensure multi material support
        addGroup( groupStart, groupCount, 0 );

        // calculate new start value for groups
        groupStart += groupCount;
    };

    generateTorso();
    auto generateCap = [&]( bool top )
    {

        // save the index of the first center vertex
        auto centerIndexStart = index;

        Vector2 uv;
        Vector3 vertex;

        size_t groupCount = 0;

        Real radius = ( top ) ? radiusTop : radiusBottom;
        Real sign = ( top ) ? 1 : - 1;

        // first we generate the center vertex data of the cap.
        // because the geometry needs one set of uvs per face,
        // we must generate a center vertex per face/segment

        for ( uint32_t x = 1; x <= radialSegments; x++ )
        {
            // vertex
            vertices.push_back( 0);
            vertices.push_back(halfHeight * sign);
            vertices.push_back(0);

            // normal
            normals.push_back( 0);
            normals.push_back(sign);
            normals.push_back( 0);

            // uv
            uvs.push_back( 0.5);
            uvs.push_back( 0.5);

            // increase index
            ++index;
        }

        // save the index of the last center vertex
        auto centerIndexEnd = index;

        // now we generate the surrounding vertices, normals and uvs
        for ( uint32_t x = 0; x <= radialSegments; x ++ )
        {
            Real u = Real(x) / radialSegments;
            Real theta = u * thetaLength + thetaStart;

            Real cosTheta = std::cos( theta );
            Real sinTheta = std::sin( theta );

            // vertex

            vertex.x = radius * sinTheta;
            vertex.y = halfHeight * sign;
            vertex.z = radius * cosTheta;
            vertices.push_back( vertex.x);
            vertices.push_back( vertex.y);
            vertices.push_back( vertex.z);

            // normal
            normals.push_back( 0);
            normals.push_back(sign);
            normals.push_back( 0);

            // uv
            uv.x = ( cosTheta * 0.5 ) + 0.5;
            uv.y = ( sinTheta * 0.5 * sign ) + 0.5;
            uvs.push_back( uv.x);
            uvs.push_back( uv.y);

            // increase index
            ++index ;
        }

        // generate indices
        for ( uint32_t x = 0; x < radialSegments; ++x )
        {
            auto c = centerIndexStart + x;
            auto i = centerIndexEnd + x;

            if ( top)
            {
                // face top
                indices.push_back( i);
                indices.push_back( i + 1);
                indices.push_back( c );
            }
            else
            {
                // face bottom
                indices.push_back( i + 1);
                indices.push_back( i);
                indices.push_back( c );
            }

            groupCount += 3;

        }

        // add a group to the geometry. this will ensure multi material support
        addGroup( groupStart, groupCount, top == true ? 1 : 2 );

        // calculate new start value for groups
        groupStart += groupCount;

    };

    if ( openEnded == false )
    {
        if ( radiusTop > 0 ) generateCap( true );
        if ( radiusBottom > 0 ) generateCap( false );
    }

    // build geometry
    this->setIndices(new UInt32BufferAttribute(indices,3));
    setAttribute( "position", new Float32BufferAttribute( vertices, 3 ) );
    setAttribute( "normal", new Float32BufferAttribute( normals, 3 ) );
    setAttribute( "uv", new Float32BufferAttribute( uvs, 2 ) );
}
POCELAIN_NAMESPACE_END
